﻿using System;
using System.Windows.Controls;
using System.Windows.Ink;
using ThePresenter.Net.NetCom;
using ThePresenter.Net.PresenterCom.Actions;
using System.Windows.Forms;
using ThePresenter.Net.PresenterCom.Uids;
using System.Windows;
using ThePresenter.Polls;

namespace ThePresenter.Net.PresenterCom
{
	class PresentationHost : PresentationNetworkClient
	{
		private Guid _HostGuid;

		private DebugClass _D;

		private InkCanvas _BoundCanvas;
		private Presentation _BoundPresentation;

		private StrokeCollectionChangedEventHandler _Strokes_StrokesChangedEventHandler;
		private InkCanvasSelectionEditingEventHandler _Canvas_SelectionEditingEventHandler;

		private RoutedPropertyChangedEventHandler<Slide> _BoundPresentation_SlideAddedHandler;
		private RoutedPropertyChangedEventHandler<Slide> _BoundPresentation_SelectedSlideChangedHandler;


		private NetComServer _NCS;

		public Boolean IsBound
		{
			get { 
				return _BoundCanvas != null; 
			}
		}

		private Boolean _ShouldTryToSend
		{
			get { return _NCS.HasClients; }
		}

		/// <summary>
		/// Creates host for presentation and starts advertising server
		/// </summary>
		/// <param name="Port">Port, server should listen on for new clients</param>
		/// <exception cref="System.Net.Sockets.SocketException">If advertising socket cannot be created</exception>
		public PresentationHost(int Port)
		{
			this._HostGuid = Guid.NewGuid();
			this._D = new DebugClass("PresentationHost");

			this._NCS = new NetComServer(Port, this._HostGuid);
			this._NCS.IncomingAction += new NetComServer.IncomingClientActionHandler(_NCS_IncomingAction);
			this._NCS.ClientAdded += new NetComServer.ClientManagedHandler(_NCS_ClientAdded);
			this._NCS.ClientRemoved += new NetComServer.ClientManagedHandler(_NCS_ClientRemoved);
			this._NCS.ClientCompleted += new NetComServer.ClientManagedHandler(_NCS_ClientCompleted);

			this._NCS.BeginListen();

			this._Canvas_SelectionEditingEventHandler = new InkCanvasSelectionEditingEventHandler(Canvas_SelectionEditing);
			this._Strokes_StrokesChangedEventHandler = new StrokeCollectionChangedEventHandler(Strokes_StrokesChanged);

			this._BoundPresentation_SlideAddedHandler = new RoutedPropertyChangedEventHandler<Slide>(_BoundPresentation_SlideAdded);
			this._BoundPresentation_SelectedSlideChangedHandler = new RoutedPropertyChangedEventHandler<Slide>(_BoundPresentation_SelectedSlideChanged);
		}

		void _NCS_IncomingAction(Uid client, PresenterAction action)
		{
			_D.Write("Incoming action: {0}", action.GetType().ToString());

			if (action is IntroductionAction)
			{
				SendAction(new ServerIntroductionAction(_NCS.ServerGuid, Properties.Settings.Default.Username), client);

				if (_BoundPresentation.SelectedSlide != null)
				{
					SendAction(new NewSlideAction(_BoundPresentation.SelectedSlide), client);
					SendAction(new SelectSlideAction(_BoundPresentation.SelectedSlide), client);
				}
			}
			else if (action is SlideRequestAction)
			{
				Uid requestedGuid = (action as SlideRequestAction)._SlideID;
				// null means server did not find requested slide
				SendAction(new SlideResponseAction(_BoundPresentation.FindSlideByUid(requestedGuid)), client);
			}
			else if (action is AnswerPollAction)
			{
				AnswerPollAction A = (AnswerPollAction)action;
				A.AnswerSource = client;
				App.Current.Dispatcher.Invoke(new Action(() => A.Execute(_BoundPresentation)));
			}
		}

		void _NCS_ClientAdded(object sender, Uid client)
		{
			ClientInfo Info = _NCS.GetClientById(client);
			// NOTE: do not sync, sync is made once client introduces himself
			if (Info.IsComplete)
				App.Current.Dispatcher.Invoke(new Action(() => _BoundPresentation.AddClient(client, Info)));
		}

		void _NCS_ClientRemoved(object sender, Uid client)
		{
			App.Current.Dispatcher.Invoke(new Action(() => _BoundPresentation.RemoveClient(client)));
		}

		void _NCS_ClientCompleted(object sender, Uid client)
		{
			ClientInfo Info = _NCS.GetClientById(client);
			App.Current.Dispatcher.Invoke(new Action(() => _BoundPresentation.UpdateClient(client, Info)));
		}

		/// <summary>
		/// Sends the action to defined recipient.
		/// </summary>
		/// <param name="action">The action.</param>
		/// <param name="recipient">The recipient.</param>
		public void SendAction(PresenterAction action, Uid recipient)
		{
			try
			{
				if (recipient != null)
					this._NCS.SendTo(action, recipient);
				else
					this._NCS.Send(action);
			}
			catch (ServerNotBroadcastingException)
			{
				// DebugClass.ShowMessage("PresentationHost.SendAction()\nMessage could not be sent : {0}", SNBE.Message);
			}
			catch (System.Runtime.Serialization.SerializationException SE)
			{
				DebugClass.ShowMessage("PresentationHost.SendAction()\nSerialization error : {0}", SE.Message);
			}
		}

		/// <summary>
		/// Sends the action to all connected clients.
		/// </summary>
		/// <param name="a">The action.</param>
		public void SendAction(PresenterAction a)
		{
			SendAction(a, null);
		}

		#region Canvas methods

		#region Presentation binding
		override public void Bind(Presentation TargetPresentation)
		{
			if (TargetPresentation.BindingMode == PresentationBindingMode.NotBind || TargetPresentation.BindingMode == PresentationBindingMode.Undefined)
			{
				this.UnbindCanvas();
				this.Unbind();

				this._BoundPresentation = TargetPresentation;
				this._BoundPresentation.BindingMode = PresentationBindingMode.PresentationHost;
				this._BoundPresentation.SelectedSlideChanged += this._BoundPresentation_SelectedSlideChangedHandler;
				this._BoundPresentation.SlideAdded += this._BoundPresentation_SlideAddedHandler;
				this._BoundPresentation.PollPublished += new Presentation.PollEventHandler(_BoundPresentation_PollPublished);
			}
			else
			{
				throw new ApplicationException("Cannot bind to presentation that is already bound.", new InvalidOperationException());
			}
		}

		private void Unbind()
		{
			if (this._BoundPresentation != null)
			{
				this._BoundPresentation.BindingMode = PresentationBindingMode.NotBind;
				this._BoundPresentation.SlideAdded -= this._BoundPresentation_SlideAddedHandler;
				this._BoundPresentation.SelectedSlideChanged -= this._BoundPresentation_SelectedSlideChangedHandler;
			}
		}
		#endregion

		#region Canvas binding
		/// <summary>
		/// Binds the specified target canvas.
		/// </summary>
		/// <param name="TargetCanvas">The target canvas.</param>
		internal void BindCanvas(InkCanvas TargetCanvas)
		{
			this.UnbindCanvas();

			this._BoundCanvas = TargetCanvas;
			this._BoundCanvas.Strokes.StrokesChanged += this._Strokes_StrokesChangedEventHandler;
			this._BoundCanvas.SelectionMoving += this._Canvas_SelectionEditingEventHandler;
			this._BoundCanvas.SelectionResizing += this._Canvas_SelectionEditingEventHandler;
		}

		public void UnbindCanvas()
		{
			if (this.IsBound)
			{
				this._BoundCanvas.Strokes.StrokesChanged -= this._Strokes_StrokesChangedEventHandler;
				this._BoundCanvas.SelectionMoving -= this._Canvas_SelectionEditingEventHandler;
				this._BoundCanvas.SelectionResizing -= this._Canvas_SelectionEditingEventHandler;
				this._BoundCanvas = null;
			}
		}

		#endregion
		void _BoundPresentation_SelectedSlideChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<Slide> e)
		{
			this.UnbindCanvas();
			this.BindCanvas(e.NewValue.Canvas);

			if (_ShouldTryToSend)
			{
				SelectSlideAction SSA = new SelectSlideAction(e.NewValue);
				_D.Write("Selected slide changed. Sending SelectSlideAction.");
				this.SendAction(SSA);
			}
			else
			{
				_D.Write("Selected slide changed. Souldn't try to send action so not sending.");
			}
		}

		void _BoundPresentation_SlideAdded(object sender, System.Windows.RoutedPropertyChangedEventArgs<Slide> e)
		{
			if (_ShouldTryToSend)
			{
				NewSlideAction SA = new NewSlideAction(e.NewValue);
				_D.Write("New slide created. Sending NewSlideAction.");
				this.SendAction(SA);
			}
			else
			{
				_D.Write("New slide created. Souldn't try to send action so not sending.");
			}
		}

		void _BoundPresentation_PollPublished(object sender, Poll p)
		{
			if (_ShouldTryToSend)
			{
				PublishPollAction PPA = new PublishPollAction(p);
				_D.Write("New poll published. Sending PublishPollAction.");
				this.SendAction(PPA);
			}
			else
			{
				_D.Write("New poll published. Souldn't try to send action so not sending.");
			}
		}

		#region Strokes editing detection

		void Canvas_SelectionEditing(object sender, InkCanvasSelectionEditingEventArgs e)
		{
			if (_ShouldTryToSend)
			{
				InkCanvas Sender = sender as InkCanvas;
				ManipulationAction MA = new ManipulationAction(Sender.GetSelectedStrokes(), e.NewRectangle, e.OldRectangle);
				this.SendAction(MA);
			}
			else
			{
				_D.Write("Selection editing. Souldn't try to send action so not sending.");
			}
		}

		void Strokes_StrokesChanged(object sender, System.Windows.Ink.StrokeCollectionChangedEventArgs e)
		{
			foreach (Stroke S in e.Added)
			{
				S.SetUid(new Uid());
			}

			if (_ShouldTryToSend && _BoundPresentation.FindSlideByUid(_BoundPresentation.SelectedSlide.Uid) != null) // TODO: prerobit efektivnejsie
			{
				DrawingAction DA = new DrawingAction(new StrokeCollection(e.Added), new UidCollection(e.Removed));
				this.SendAction(DA);
			}
			else
			{
				_D.Write("Strokes changed. Souldn't try to send action so not sending.");
			}
		}

		#endregion

		#endregion

		override public void Dispose()
		{
			this._NCS.Dispose();
		}
	}
}
